Mongoose 是一个将JavaScript对象与数据库产生关系的一个框架,object related model。操作对象,就是操作数据库了;对象产生了,同时也持久化了。
这个思路是Java三大框架SSH中Hibernate框架的思路。彻底改变了人们使用数据库的方式。官网
官网上的 hello world 栗子:
1 | //引包,并不需要引用mongodb这个包 |
上面的代码中,没有一个语句是明显的操作数据库,感觉都在创建类、实例化类、调用类的方法。都在操作对象,但是数据库同步被持久了。
mongoose的哲学,就是让你用操作对象的方式操作数据库。
创建一个模型:
1 | mongoose.model("Cat",{"name" : String , "age" : Integer}); |
就可以被实例化:
1 | var kitty = new Cat({ name: 'Zildjian' }); |
然后这个实例就可以被 save:
1 | kitty.save(function (err) { |
你需要牢记的就是:使用 mongoose 进行的所有操作都不是对数据库进行的,都是对类、实例进行的。但是数据库的持久化自动完成了。
Schema 之所以能够定义documents, 是因为他可以限制你输入的字段及其类型. mongoose支持的基本类型有:
- String
- Number
- Date
- Buffer
- Boolean
- Mixed
- ObjectId
- Array
数据库连接方式
连接数据库并获取数据库对象 db 的两种方式:
1 | const mongoose = require('mongoose'); |
1 | var mongoose = require('mongoose'); |
mongoose.createConnection() 和 mongoose.connect() 区别:
首先,我们需要定义一个连接,如果你的app只用到一个数据库,你应该使用 mongoose.connect。如果你还需要连接其他数据库,使用 mongoose.createConnection。
所有的 connect and createConnection 使用 mongodb:// URI, or the parameters host, database, port, options.等参数
一旦连接,Connection对象会触发open事件,如果你使用 mongoose.connect, Connection 对象就是默认的 mongoose.connection,而使用 mongoose.createConnection 返回值就是 Connection.
注意:Mongoose会缓存所有命令直到连接上数据库,这意味着你不必等待它连接MongoDB再定义 models,执行 queries 等。
定义模型 schema
创造schema → 定义一些schema的静态方法 → 创造模型
创造schema用什么语句? new mongoose.schema({});
创造模型用什么语句? db.model(“Student”,schema名字);
例如一个学生类的创建与使用:
模型类 Student.js
1 | var mongoose = require('mongoose'); |
在这里, mongoose.model里面定义的第一个参数,比如’Student’, 并不是数据库中的, collection. 他只是collection的单数形式, 实际上在db中的collection是’Students’.
想两边名称保持一致,可参考 Mongoose在创建Model时对Collection的命名策略
创建连接数据库 db.js
1 | //引包 |
使用程序 app.js
1 | //定义了一个模型,学生模型,“学生类” |
SchemaType
ype属性指定SchemaType类型,不同的SchemaType类型还有其他不同的属性配置:
1 | var schema2 = new Schema({ |
公有的类型有:
- required: 必选验证器。
- default: 默认值。Any或function,如果该值是一个函数,则该函数的返回值将用作默认值。
- select: boolean值, 指定是否被投影
- validate: 验证器
- get: get方法,using Object.defineProperty()
- set: set方法 using Object.defineProperty()
- alias: 别名
设置索引
这里设置索引分两种,一种设在Schema filed, 另外一种设在 Schema.index 里:
在 field 设置:
1 | var testSchema = new Schema({ |
在Schema.index中设置:
1 | //1 表示正序, -1 表示逆序 |
两者效果是一样的,不过推荐直接在Schema level中设置, 这样分开能够增加可读性。
当应用启动的时候, Mongoose 会自动为 Schema 中每个定义了索引的调用 ensureIndex,确保生成索引,并在所有的 secureIndex 调用成功或出现错误时,在 Model 上发出一个’index’事件。 开发环境用这个很好, 但是建议在生产环境不要使用这个,因为这样可能严重拖慢查询或者创建速度,所以一般而言,我们需要将该option 关闭.使用下面的方法禁用 ensureIndex:
1 | mongoose.connect('mongodb://user:pass@localhost:port/database', { config: { autoIndex: false } }); //真心推荐 |
Schema.statics与Schema.methods
Model的静态方法,即可以在 Model 上调用 相关的查询或者删除:
1 | // 给model添加一个find方法 |
虚拟属性
Mongoose 还有一个super featrue– virtual property 该属性是直接设置在Schema上的. 但是,需要注意的是,VR 并不会真正的存放在db中. 他只是一个提取数据的方法。
1 | //schema基本内容 |
但是,像这样,仅仅这是为了获取一个属性, 实际上完全可以使用虚拟属性来实现:
1 | //schema 添加虚拟属性 |
而且,经过测试, 使用fn实现的返回,比VR 要慢几十倍. 一下是测试结果:
1 | console.time(1); |
最后再补充一下,Schema中初始化的相关参数.
Schema参数 在 new Schema([options]) 中,我们需要设置一些相关的参数:
safe: 用来设置安全模式. 实际上,就是定义入库时数据的写入限制. 比如写入时限等:
1 | //使用安全模式. 表示在写入操作时,如果发生错误,也需要返回信息. |
toObject: 用来表示在提取数据的时候, 把documents 内容转化为Object内容输出. 一般而言只需要设置getters为true即可:
1 | r schema = new Schema({ name: String }); |
toJSON: 该是和toObject一样的使用. 通常用来把 documents 转化为Object. 但是, 需要显示使用toJSON()方法:
1 | var schema = new Schema({ name: String }); |
model 的子文档操作
本来mongodb是没有关系的. 但是, mongoose提供了children字段. 让我们能够轻松的在表间建立关系. 现在,我们来创建一个子域:
1 | var childSchema = new Schema({ name: 'string' }); |
现在, 我们就已经创建了3个table. 一个parent 包含了 两个child 另外,如果我们想要查询指定的doc。 则可以使用 id()方法:
1 | var doc = parent.children.id(id); |
子文档的CRUD, 实际上就是数组的操作, 比如push,unshift,remove,pop,shift等:
1 | parent.children.push({ name: 'Liesl' }); |
mongoose还给移除提供了另外一个方法–remove:
1 | var doc = parent.children.id(id).remove(); |
如果你忘记添加子文档的话,可以在外围添加, 但是字段必须在Schema中指定:
1 | var newdoc = parent.children.create({ name: 'Aaron' }); |
document的CRUD操作
document的创建
document 的创建 关于document的创建,有两种方法, 一种是使用document实例创建,另外一种是使用Model类创建:
1 | var Tank = mongoose.model('Tank', yourSchema); |
document的查询
Mongoose查找文档很容易,它支持MongoDB的丰富的查询语法。 可以使用每个models find,findById,findOne或where 等静态方法进行查找文档。事实上,在mongoose中,query数据 提供了两种方式:
callback: 使用回调函数, 即query会立即执行,然后返回到回调函数中,其中‘name occupation’为设置查询选项:
1 | Person.findOne({ 'name.last': 'Ghost' }, 'name occupation', function (err, person) { |
query: 使用查询方法,返回的是一个Query对象. 该对象是一个Promise, 所以可以使用 chain 进行调用.最后必须使用exec(cb)传入回调进行处理. cb 是一个套路, 第一个参数永远是err. 第二个就是返回的数据。
1 | Tank.find({ size: 'small' }).where('createdDate').gt(oneYearAgo).exec(callback); |
以下两种等价写法:
1 | // With a JSON doc |
Query Helpers:你能够添加 query helper functions,跟定义在Schema实例方法一样,但是返回query对象作为mongoose queries使用(说句白了就是封装mongoose查询方法). Query helper methods 使你能够扩展mongoose’s chainable query builder API:
1 | animalSchema.query.byName = function(name) { |
上面4个API, 3个使用方式都是一样的, 另外一个不同的是where. 他一样是用来进行query. 只是,写法和find系列略有不同:
1 | User.find({age: {$gte: 21, $lte: 65}}, callback); |
另外还有一些游标集合的处理方法: 常用的就3个, limit,skip,sort,跟原生mongoDB提供的一样:
1 | //limit:用来获取限定长度的内容. |
document删除
reomve操作仅在通过回调时执行。 要强制执行没有回调,您必须先调用remove(),然后使用exec()方法执行它。
我们可以在document上执行remove方法也可以在Model上。
1 | Model.find().remove({ name: 'Anne Murray' }, callback) |
document更新
使用Model.update([(conditions, doc, [options], [callback])]
不返回更新对象到应用程序。如果要更新数据库中的单个文档并将其返回到应用程序,请改用findOneAndUpdate。
参数说明:
- conditions: 就是query. 通过query获取到指定doc
- doc: 就是用来替换doc内容的值.
- options:
- safe (boolean) 是否开启安全模式 (default for true)
- upsert (boolean) 如果没有匹配到内容,是否自动创建 ( default for false)
- multi (boolean) 如果有多个doc,匹配到,是否一起更改 ( default for false)
- strict (boolean) 使用严格模式(default for false)
- overwrite (boolean) 匹配到指定doc,是否覆盖 (default for false)
- runValidators (boolean): 表示是否用来启用验证. 实际上,你首先需要写一个验证. 关于如果书写,验证大家可以参考下文, validate篇(default for false)
new(使用findOneAndUpdate时才有参数):bool - 如果为true,则返回修改后的文档而不是原始文件。 默认为false。
1 | Model.update({age:18}, { $set: { name: 'jason borne' }}, {multi:true}, function (err, raw) { |
其中的$set是,用来指明更新的字段。
中间件
mongoose里的中间件,有两个, 一个是pre, 一个是post:
- pre: 在指定方法执行之前绑定。 中间件的状态分为 parallel 和 series
- post: 相当于事件监听的绑定
这里需要说明一下, 中间件一般仅仅只能限于在几个方法中使用. (但感觉就已经是全部了):
- doc 方法上: init,validate,save,remove;
- model方法上: count,find,findOne,findOneAndRemove,findOneAndUpdate,update
pre
我们来看一下 pre 中间件是如何绑定的:
串行:
1 | var schema = new Schema(..); |
并行:
1 | var schema = new Schema(..); |
post
post会在指定事件后触发,就像事件监听器一样,post钩子没什么控制流程,即它是异步的。
1 | schema.post('save', function(doc) { |
当save方法调用时, 便会触发post绑定的save事件。假如你绑定了多个post。 也可以需要指定一下中间件顺序:
1 | // Takes 2 parameters: this is an asynchronous post hook |
Validation验证器
验证器在SchemaType中定义。
Validation 是一种中间件,Mongoose 触发 validation 同 a pre(‘save’)钩子一样 。你能够手动触发 validation 通过doc.validate(callback) or doc.validateSync()。
1 | cat.save(function(error) { |
内置验证器
Mongoose 有一些列内置验证器:
- 所有的SchemaTypes都有required验证器
- min,max: 用来给Number类型的数据设置限制
1 | var breakfastSchema = new Schema({ |
- enum,match,maxlength,minlength: 这些验证是给string类型的. enum 就是枚举,表示该属性值,只能出席那那些. match是用来匹配正则表达式的. maxlength&minlength 显示字符串的长度。
1 | new Schema({ |
自定义验证器
如果内置验证器不够,您可以定义自定义验证器以满足您的需要:
1 | // 创建验证器 |
验证错误对象
验证失败后返回的错误包含一个包含实际ValidatorError对象的错误对象。 每个ValidatorError都有kind,path,value和message属性:
1 | var toySchema = new Schema({ |
更新验证器
在Model.update那一节有个参数–runValidators. 还没有详细说. 这里, 展开一下. 实际上, validate一般只会应用在save上, 如果你想在update使用的话, 需要额外的trick,而runValidators就是这个trick。
Mongoose 还支持 update()和findOneAndUpdate()操作的验证。 在 Mongoose 4.x 中,更新验证器默认关闭 - 您需要指定 runValidators 选项。
1 | var opts = { runValidators: true }; |
population
mongoDB 本来就是一门非关系型数据库。 但有时候,我们又需要联合其他的table进行数据查找。 mongoose提供的 population. 用来连接多表数据查询. 一般而言, 我们只要提供某一个 collection的 _id , 就可以实现完美的联合查询. population 用到的关键字是: ref 用来指明外联的数据库的名字. 一般,我们需要在 schema 中就定义好.
1 | var mongoose = require('mongoose') |
注意:ObjectId,Number, String, 和 Buffer 都可以作为 refs 使用。
使用populate query方法进行关联
1 | Story |
完整的增删改查小例子
1 | // mongoose 链接 |
完~
参考链接:Mongoose初使用总结
最后增删改查例子引用:node.js下mongoose简单操作实例